#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/gpio/consumer.h>

#define HELLO_DBG
#ifdef HELLO_DBG
#define hello_dbg(fmt, ...) printk("[%s][%d]" fmt "\r\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define hello_dbg(fmt, ...)
#endif

#define MODULE_NAME "hello_nanopi_led"
#define DEV_NAME "hello_led"
#define CLASS_NAME "hello_leds"

struct hello_nanopi_led_desc
{
    struct gpio_desc *status;
    dev_t devt;
    struct cdev *pcdev;
    struct class *hello_cls;
    struct device *hello_dev;
};

static struct hello_nanopi_led_desc hello_desc = {};

static ssize_t hello_read(struct file *file, char __user *user, size_t len, loff_t *loff)
{
    int ret;
    int value;

    hello_dbg("");

    value = gpiod_get_value(hello_desc.status);

    ret = copy_to_user(user, &value, sizeof(value));
    if (ret == 0)
    {
        return sizeof(value);
    }
    else
    {
        hello_dbg("copy_to_user err, %d", ret);
        return ret;
    }

    return 0;
}

static ssize_t hello_write(struct file *file, const char __user *user, size_t len, loff_t *loff)
{
    int ret;
    int value;

    hello_dbg("");

    ret = copy_from_user(&value, user, sizeof(value));
    if (ret)
    {
        hello_dbg("copy_from_user err, %d", ret);
        return ret;
    }

    hello_dbg("write value, %d", value);

    gpiod_set_value(hello_desc.status, value);

    return sizeof(value);
}

static int hello_open(struct inode *inode, struct file *file)
{
    hello_dbg("");

    return 0;
}

static int hello_release(struct inode *inode, struct file *file)
{
    hello_dbg("");

    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = hello_read,
    .write = hello_write,
    .open = hello_open,
    .release = hello_release,
};

static int hello_nanopi_led_probe(struct platform_device *pdev)
{
    int ret;

    hello_dbg("");

    hello_desc.status = devm_gpiod_get(&pdev->dev, "status", GPIOD_OUT_HIGH);
    if (IS_ERR(hello_desc.status))
    {
        dev_err(&pdev->dev, "failed to get status led gpio from dts\n");
        return PTR_ERR(hello_desc.status);
    }

    ret = gpiod_direction_output(hello_desc.status, 0);
    if (ret)
    {
        dev_err(&pdev->dev, "failed to set let gpio\n");
        return ret;
    }

    ret = alloc_chrdev_region(&hello_desc.devt, 0, 1, DEV_NAME);
    if (ret)
    {
        hello_dbg("register_chrdev_region error:%d", ret);
        goto err0;
    }

    hello_desc.pcdev = cdev_alloc();
    if (hello_desc.pcdev == NULL)
    {
        hello_dbg("cdev_alloc error");
        ret = -ENOMEM;
        goto err1;
    }

    cdev_init(hello_desc.pcdev, &fops);

    ret = cdev_add(hello_desc.pcdev, hello_desc.devt, 1);
    if (ret)
    {
        hello_dbg("cdev_add error:%d", ret);
        goto err2;
    }

    hello_desc.hello_cls = class_create(CLASS_NAME);
    if (IS_ERR(hello_desc.hello_cls))
    {
        hello_dbg("class_create error");
        ret = PTR_ERR(hello_desc.hello_cls);
        goto err2;
    }

    hello_desc.hello_dev = device_create(hello_desc.hello_cls, NULL, hello_desc.devt, NULL, DEV_NAME);
    if (IS_ERR(hello_desc.hello_cls))
    {
        hello_dbg("device_create error");
        ret = PTR_ERR(hello_desc.hello_dev);
        goto err3;
    }

    return 0;

err3:
    class_destroy(hello_desc.hello_cls);
err2:
    cdev_del(hello_desc.pcdev);
    if (hello_desc.pcdev)
    {
        kfree(hello_desc.pcdev);
        hello_desc.pcdev = NULL;
    }
err1:
    unregister_chrdev_region(hello_desc.devt, 1);
err0:
    return ret;
}

static int hello_nanopi_led_remove(struct platform_device *pdev)
{
    int ret;

    hello_dbg("");

    device_destroy(hello_desc.hello_cls, hello_desc.devt);
    class_destroy(hello_desc.hello_cls);
    cdev_del(hello_desc.pcdev);
    if (hello_desc.pcdev)
    {
        kfree(hello_desc.pcdev);
        hello_desc.pcdev = NULL;
    }
    unregister_chrdev_region(hello_desc.devt, 1);

    ret = gpiod_direction_output(hello_desc.status, 0);
    if (ret)
    {
        dev_err(&pdev->dev, "failed to set let gpio\n");
        return ret;
    }

    return 0;
}

static const struct of_device_id hello_nanopi_led_table[] = {
    {.compatible = "hello,nanopi_neo_led"},
    {},
};

static struct platform_driver dri = {
    .probe = hello_nanopi_led_probe,
    .remove = hello_nanopi_led_remove,
    .driver = {
        .name = MODULE_NAME,
        .of_match_table = hello_nanopi_led_table,
    },
};

module_platform_driver(dri);

MODULE_AUTHOR("ljq");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test nanopi neo led driver");
